import AXorcist
import Commander
import Foundation
import PeekabooCore
import PeekabooFoundation

/// Interact with system dialogs and alerts
@MainActor
struct DialogCommand: ParsableCommand {
    static let commandDescription = CommandDescription(
        commandName: "dialog",
        abstract: "Interact with system dialogs and alerts",
        discussion: """

        EXAMPLES:
          # Click a button in a dialog
          peekaboo dialog click --button "OK"
          peekaboo dialog click --button "Don't Save"

          # Type in a dialog text field
          peekaboo dialog input --text "password123" --field "Password"

          # Handle file dialogs
          peekaboo dialog file --path "/Users/me/Documents/file.txt"
          peekaboo dialog file --name "report.pdf" --select "Save"

          # Dismiss dialogs
          peekaboo dialog dismiss
          peekaboo dialog dismiss --force  # Press Escape
        """,
        subcommands: [
            ClickSubcommand.self,
            InputSubcommand.self,
            FileSubcommand.self,
            DismissSubcommand.self,
            ListSubcommand.self,
        ],
        showHelpOnEmptyInvocation: true
    )

    // MARK: - Click Dialog Button

    @MainActor

    struct ClickSubcommand {
        @Option(help: "Button text to click (e.g., 'OK', 'Cancel', 'Save')")
        var button: String

        @Option(help: "Specific window/sheet title to target")
        var window: String?

        @Option(help: "Application hosting the dialog (focus hint)")
        var app: String?
        @RuntimeStorage private var runtime: CommandRuntime?

        private var resolvedRuntime: CommandRuntime {
            guard let runtime else {
                preconditionFailure("CommandRuntime must be configured before accessing runtime resources")
            }
            return runtime
        }

        private var services: any PeekabooServiceProviding { self.resolvedRuntime.services }
        private var logger: Logger { self.resolvedRuntime.logger }
        var outputLogger: Logger { self.logger }
        var jsonOutput: Bool { self.resolvedRuntime.configuration.jsonOutput }

        @MainActor
        mutating func run(using runtime: CommandRuntime) async throws {
            self.runtime = runtime
            self.logger.setJsonOutputMode(self.jsonOutput)

            do {
                // Provide both app and window hints so dialog detection can focus nested sheets.
                await DialogCommand.focusDialogAppIfNeeded(
                    appName: self.app,
                    windowTitle: self.window,
                    services: self.services,
                    logger: self.logger
                )

                // Click the button using the service
                let result = try await self.services.dialogs.clickButton(
                    buttonText: self.button,
                    windowTitle: self.window,
                    appName: self.app
                )

                // Output result
                if self.jsonOutput {
                    struct DialogClickResult: Codable {
                        let action: String
                        let button: String
                        let window: String
                    }

                    let outputData = DialogClickResult(
                        action: "dialog_click",
                        button: result.details["button"] ?? self.button,
                        window: result.details["window"] ?? "Dialog"
                    )
                    outputSuccessCodable(data: outputData, logger: self.outputLogger)
                } else {
                    print("✓ Clicked '\(result.details["button"] ?? self.button)' button")
                }
                AutomationEventLogger.log(
                    .dialog,
                    "action=click button='\(result.details["button"] ?? self.button)' "
                        + "window='\(result.details["window"] ?? self.window ?? "unknown")' "
                        + "app='\(self.app ?? "unknown")'"
                )

            } catch let error as DialogError {
                handleDialogServiceError(error, jsonOutput: self.jsonOutput, logger: self.outputLogger)
                throw ExitCode(1)
            } catch {
                handleGenericError(error, jsonOutput: self.jsonOutput, logger: self.outputLogger)
                throw ExitCode(1)
            }
        }
    }

    // MARK: - Input Text in Dialog

    @MainActor
    struct InputSubcommand {
        static let commandDescription = CommandDescription(
            commandName: "input",
            abstract: "Enter text in a dialog field using DialogService"
        )

        @Option(help: "Text to enter")
        var text: String

        @Option(help: "Field label or placeholder to target")
        var field: String?

        @Option(help: "Field index (0-based) if multiple fields")
        var index: Int?

        @Option(help: "Window or sheet title to target")
        var window: String?

        @Flag(help: "Clear existing text first")
        var clear = false

        @Option(help: "Application hosting the dialog (focus hint)")
        var app: String?
        @RuntimeStorage private var runtime: CommandRuntime?

        private var resolvedRuntime: CommandRuntime {
            guard let runtime else {
                preconditionFailure("CommandRuntime must be configured before accessing runtime resources")
            }
            return runtime
        }

        private var services: any PeekabooServiceProviding { self.resolvedRuntime.services }
        private var logger: Logger { self.resolvedRuntime.logger }
        var outputLogger: Logger { self.logger }
        var jsonOutput: Bool { self.resolvedRuntime.configuration.jsonOutput }

        @MainActor
        mutating func run(using runtime: CommandRuntime) async throws {
            self.runtime = runtime
            self.logger.setJsonOutputMode(self.jsonOutput)

            do {
                await DialogCommand.focusDialogAppIfNeeded(
                    appName: self.app,
                    windowTitle: self.window,
                    services: self.services,
                    logger: self.logger
                )

                // Determine field identifier (index or label)
                let fieldIdentifier = self.field ?? self.index.map { String($0) }

                // Enter text using the service
                let result = try await self.services.dialogs.enterText(
                    text: self.text,
                    fieldIdentifier: fieldIdentifier,
                    clearExisting: self.clear,
                    windowTitle: self.window,
                    appName: self.app
                )

                // Output result
                if self.jsonOutput {
                    struct DialogInputResult: Codable {
                        let action: String
                        let field: String
                        let textLength: String
                        let cleared: String
                    }

                    let outputData = DialogInputResult(
                        action: "dialog_input",
                        field: result.details["field"] ?? "Text Field",
                        textLength: result.details["text_length"] ?? String(self.text.count),
                        cleared: result.details["cleared"] ?? String(self.clear)
                    )
                    outputSuccessCodable(data: outputData, logger: self.outputLogger)
                } else {
                    print("✓ Entered text in '\(result.details["field"] ?? "field")'")
                }
                let fieldDescription = result.details["field"]
                    ?? self.field
                    ?? self.index.map { "index \($0)" }
                    ?? "field"
                let textLength = result.details["text_length"] ?? String(self.text.count)
                let clearedValue = result.details["cleared"] ?? String(self.clear)
                AutomationEventLogger.log(
                    .dialog,
                    "action=input field='\(fieldDescription)' chars=\(textLength) "
                        + "cleared=\(clearedValue) app='\(self.app ?? "unknown")'"
                )

            } catch let error as DialogError {
                handleDialogServiceError(error, jsonOutput: self.jsonOutput, logger: self.outputLogger)
                throw ExitCode(1)
            } catch {
                handleGenericError(error, jsonOutput: self.jsonOutput, logger: self.outputLogger)
                throw ExitCode(1)
            }
        }
    }

    // MARK: - Handle File Dialog

    @MainActor
    struct FileSubcommand {
        static let commandDescription = CommandDescription(
            commandName: "file",
            abstract: "Handle file save/open dialogs using DialogService"
        )

        @Option(help: "Full file path to navigate to")
        var path: String?

        @Option(help: "File name to enter (for save dialogs)")
        var name: String?

        @Option(help: "Button to click after entering path/name")
        var select: String = "Save"

        @Option(help: "Application hosting the dialog (focus hint)")
        var app: String?
        @RuntimeStorage private var runtime: CommandRuntime?

        private var resolvedRuntime: CommandRuntime {
            guard let runtime else {
                preconditionFailure("CommandRuntime must be configured before accessing runtime resources")
            }
            return runtime
        }

        private var services: any PeekabooServiceProviding { self.resolvedRuntime.services }
        private var logger: Logger { self.resolvedRuntime.logger }
        var outputLogger: Logger { self.logger }
        var jsonOutput: Bool { self.resolvedRuntime.configuration.jsonOutput }

        @MainActor
        mutating func run(using runtime: CommandRuntime) async throws {
            self.runtime = runtime
            self.logger.setJsonOutputMode(self.jsonOutput)

            do {
                await DialogCommand.focusDialogAppIfNeeded(
                    appName: self.app,
                    windowTitle: nil,
                    services: self.services,
                    logger: self.logger
                )

                // Handle file dialog using the service
                let result = try await self.services.dialogs.handleFileDialog(
                    path: self.path,
                    filename: self.name,
                    actionButton: self.select,
                    appName: self.app
                )

                // Output result
                if self.jsonOutput {
                    struct FileDialogResult: Codable {
                        let action: String
                        let path: String?
                        let name: String?
                        let buttonClicked: String
                    }

                    let outputData = FileDialogResult(
                        action: "file_dialog",
                        path: result.details["path"],
                        name: result.details["filename"],
                        buttonClicked: result.details["button_clicked"] ?? self.select
                    )
                    outputSuccessCodable(data: outputData, logger: self.outputLogger)
                } else {
                    print("✓ Handled file dialog")
                    if let p = result.details["path"] { print("  Path: \(p)") }
                    if let n = result.details["filename"] { print("  Name: \(n)") }
                    print("  Action: \(result.details["button_clicked"] ?? self.select)")
                }
                let resolvedPath = result.details["path"] ?? self.path ?? "unknown"
                let resolvedName = result.details["filename"] ?? self.name ?? "unknown"
                let buttonClicked = result.details["button_clicked"] ?? self.select
                AutomationEventLogger.log(
                    .dialog,
                    "action=file path='\(resolvedPath)' name='\(resolvedName)' "
                        + "button='\(buttonClicked)' app='\(self.app ?? "unknown")'"
                )

            } catch let error as DialogError {
                handleDialogServiceError(error, jsonOutput: self.jsonOutput, logger: self.outputLogger)
                throw ExitCode(1)
            } catch {
                handleGenericError(error, jsonOutput: self.jsonOutput, logger: self.outputLogger)
                throw ExitCode(1)
            }
        }
    }

    // MARK: - Dismiss Dialog

    @MainActor
    struct DismissSubcommand {
        static let commandDescription = CommandDescription(
            commandName: "dismiss",
            abstract: "Dismiss a dialog using DialogService"
        )

        @Flag(help: "Force dismiss with Escape key")
        var force = false

        @Option(help: "Specific window/sheet title to target")
        var window: String?

        @Option(help: "Application hosting the dialog (focus hint)")
        var app: String?
        @RuntimeStorage private var runtime: CommandRuntime?

        private var resolvedRuntime: CommandRuntime {
            guard let runtime else {
                preconditionFailure("CommandRuntime must be configured before accessing runtime resources")
            }
            return runtime
        }

        private var services: any PeekabooServiceProviding { self.resolvedRuntime.services }
        private var logger: Logger { self.resolvedRuntime.logger }
        var outputLogger: Logger { self.logger }
        var jsonOutput: Bool { self.resolvedRuntime.configuration.jsonOutput }

        @MainActor
        mutating func run(using runtime: CommandRuntime) async throws {
            self.runtime = runtime
            self.logger.setJsonOutputMode(self.jsonOutput)

            do {
                await DialogCommand.focusDialogAppIfNeeded(
                    appName: self.app,
                    windowTitle: self.window,
                    services: self.services,
                    logger: self.logger
                )

                // Dismiss dialog using the service
                let result = try await self.services.dialogs.dismissDialog(
                    force: self.force,
                    windowTitle: self.window,
                    appName: self.app
                )

                // Output result
                if self.jsonOutput {
                    struct DialogDismissResult: Codable {
                        let action: String
                        let method: String
                        let button: String?
                    }

                    let outputData = DialogDismissResult(
                        action: "dialog_dismiss",
                        method: result.details["method"] ?? "unknown",
                        button: result.details["button"]
                    )
                    outputSuccessCodable(data: outputData, logger: self.outputLogger)
                } else {
                    if result.details["method"] == "escape" {
                        print("✓ Dismissed dialog with Escape")
                    } else if let button = result.details["button"] {
                        print("✓ Dismissed dialog by clicking '\(button)'")
                    } else {
                        print("✓ Dismissed dialog")
                    }
                }
                let method = result.details["method"] ?? (self.force ? "escape" : "button")
                let dismissedButton = result.details["button"] ?? "none"
                AutomationEventLogger.log(
                    .dialog,
                    "action=dismiss method=\(method) button='\(dismissedButton)' "
                        + "app='\(self.app ?? "unknown")'"
                )

            } catch let error as DialogError {
                handleDialogServiceError(error, jsonOutput: self.jsonOutput, logger: self.outputLogger)
                throw ExitCode(1)
            } catch {
                handleGenericError(error, jsonOutput: self.jsonOutput, logger: self.outputLogger)
                throw ExitCode(1)
            }
        }
    }

    // MARK: - List Dialog Elements

    @MainActor
    struct ListSubcommand {
        static let commandDescription = CommandDescription(
            commandName: "list",
            abstract: "List elements in current dialog using DialogService"
        )

        @Option(help: "Specific window/sheet title to target")
        var window: String?

        @Option(help: "Application hosting the dialog (focus hint)")
        var app: String?
        @RuntimeStorage private var runtime: CommandRuntime?

        private var resolvedRuntime: CommandRuntime {
            guard let runtime else {
                preconditionFailure("CommandRuntime must be configured before accessing runtime resources")
            }
            return runtime
        }

        private var services: any PeekabooServiceProviding { self.resolvedRuntime.services }
        private var logger: Logger { self.resolvedRuntime.logger }
        var outputLogger: Logger { self.logger }
        var jsonOutput: Bool { self.resolvedRuntime.configuration.jsonOutput }

        /// Describe the active dialog by enumerating buttons, text fields, and static text.
        @MainActor
        mutating func run(using runtime: CommandRuntime) async throws {
            self.runtime = runtime
            self.logger.setJsonOutputMode(self.jsonOutput)

            do {
                await DialogCommand.focusDialogAppIfNeeded(
                    appName: self.app,
                    windowTitle: self.window,
                    services: self.services,
                    logger: self.logger
                )

                // List dialog elements using the service
                let elements = try await self.services.dialogs.listDialogElements(
                    windowTitle: self.window,
                    appName: self.app
                )

                // Output result
                if self.jsonOutput {
                    struct DialogListResult: Codable {
                        let title: String
                        let role: String
                        let buttons: [String]
                        let textFields: [TextField]
                        let textElements: [String]

                        struct TextField: Codable {
                            let title: String
                            let value: String
                            let placeholder: String
                        }
                    }

                    let textFields = elements.textFields.map { field in
                        DialogListResult.TextField(
                            title: field.title ?? "",
                            value: field.value ?? "",
                            placeholder: field.placeholder ?? ""
                        )
                    }

                    let outputData = DialogListResult(
                        title: elements.dialogInfo.title,
                        role: elements.dialogInfo.role,
                        buttons: elements.buttons.map(\.title),
                        textFields: textFields,
                        textElements: elements.staticTexts
                    )
                    outputSuccessCodable(data: outputData, logger: self.outputLogger)
                } else {
                    print("Dialog: \(elements.dialogInfo.title)")

                    if !elements.buttons.isEmpty {
                        print("\nButtons:")
                        elements.buttons.forEach { print("  • \($0.title)") }
                    }

                    if !elements.textFields.isEmpty {
                        print("\nText Fields:")
                        for field in elements.textFields {
                            let title = field.title ?? "Untitled"
                            let placeholder = field.placeholder ?? ""
                            print("  • \(title) [\(placeholder)]")
                        }
                    }

                    if !elements.staticTexts.isEmpty {
                        print("\nText:")
                        elements.staticTexts.forEach { print("  \($0)") }
                    }
                }
                let buttonCount = elements.buttons.count
                let textFieldCount = elements.textFields.count
                AutomationEventLogger.log(
                    .dialog,
                    "action=list title='\(elements.dialogInfo.title)' buttons=\(buttonCount) "
                        + "text_fields=\(textFieldCount) app='\(self.app ?? "unknown")'"
                )

            } catch let error as DialogError {
                handleDialogServiceError(error, jsonOutput: self.jsonOutput, logger: self.outputLogger)
                throw ExitCode(1)
            } catch {
                handleGenericError(error, jsonOutput: self.jsonOutput, logger: self.outputLogger)
                throw ExitCode(1)
            }
        }
    }

    @MainActor
    private static func focusDialogAppIfNeeded(
        appName: String?,
        windowTitle: String?,
        services: any PeekabooServiceProviding,
        logger: Logger
    ) async {
        guard let appName, !appName.isEmpty else { return }

        let target: WindowTarget = if let windowTitle, !windowTitle.isEmpty {
            .applicationAndTitle(app: appName, title: windowTitle)
        } else {
            .application(appName)
        }

        do {
            try await WindowServiceBridge.focusWindow(windows: services.windows, target: target)
            try await Task.sleep(nanoseconds: 150_000_000)
        } catch {
            if let focusError = error as? FocusError, case .windowNotFound = focusError {
                return
            }
            if let peekabooError = error as? PeekabooError, case .operationError = peekabooError {
                return
            }
            logger.debug("Dialog focus hint failed for \(appName): \(String(describing: error))")
        }
    }
}

@MainActor
extension DialogCommand.InputSubcommand: ParsableCommand {}
extension DialogCommand.InputSubcommand: AsyncRuntimeCommand {}

@MainActor
extension DialogCommand.InputSubcommand: CommanderBindableCommand {
    mutating func applyCommanderValues(_ values: CommanderBindableValues) throws {
        self.text = try values.requireOption("text", as: String.self)
        self.field = values.singleOption("field")
        self.index = try values.decodeOption("index", as: Int.self)
        self.clear = values.flag("clear")
        self.app = values.singleOption("app")
    }
}

@MainActor
extension DialogCommand.FileSubcommand: ParsableCommand {}
extension DialogCommand.FileSubcommand: AsyncRuntimeCommand {}

@MainActor
extension DialogCommand.FileSubcommand: CommanderBindableCommand {
    mutating func applyCommanderValues(_ values: CommanderBindableValues) throws {
        self.path = values.singleOption("path")
        self.name = values.singleOption("name")
        if let select = values.singleOption("select") {
            self.select = select
        }
        self.app = values.singleOption("app")
    }
}

@MainActor
extension DialogCommand.DismissSubcommand: ParsableCommand {}
extension DialogCommand.DismissSubcommand: AsyncRuntimeCommand {}

@MainActor
extension DialogCommand.DismissSubcommand: CommanderBindableCommand {
    mutating func applyCommanderValues(_ values: CommanderBindableValues) throws {
        self.force = values.flag("force")
        self.window = values.singleOption("window")
        self.app = values.singleOption("app")
    }
}

@MainActor
extension DialogCommand.ListSubcommand: ParsableCommand {}
extension DialogCommand.ListSubcommand: AsyncRuntimeCommand {}

@MainActor
extension DialogCommand.ListSubcommand: CommanderBindableCommand {
    mutating func applyCommanderValues(_ values: CommanderBindableValues) throws {
        self.window = values.singleOption("window")
        self.app = values.singleOption("app")
    }
}

@MainActor
extension DialogCommand.ClickSubcommand: ParsableCommand {
    nonisolated(unsafe) static var commandDescription: CommandDescription {
        MainActorCommandDescription.describe {
            CommandDescription(
                commandName: "click",
                abstract: "Click a button in a dialog using DialogService"
            )
        }
    }
}

extension DialogCommand.ClickSubcommand: AsyncRuntimeCommand {}

@MainActor
extension DialogCommand.ClickSubcommand: CommanderBindableCommand {
    mutating func applyCommanderValues(_ values: CommanderBindableValues) throws {
        self.button = try values.requireOption("button", as: String.self)
        self.window = values.singleOption("window")
        self.app = values.singleOption("app")
    }
}

// MARK: - Error Handling

private func handleDialogServiceError(_ error: DialogError, jsonOutput: Bool, logger: Logger) {
    let errorCode: ErrorCode = switch error {
    case .noActiveDialog:
        .NO_ACTIVE_DIALOG
    case .dialogNotFound:
        .ELEMENT_NOT_FOUND
    case .noFileDialog:
        .ELEMENT_NOT_FOUND
    case .buttonNotFound:
        .ELEMENT_NOT_FOUND
    case .fieldNotFound:
        .ELEMENT_NOT_FOUND
    case .invalidFieldIndex:
        .INVALID_INPUT
    case .noTextFields:
        .ELEMENT_NOT_FOUND
    case .noDismissButton:
        .ELEMENT_NOT_FOUND
    }

    if jsonOutput {
        let response = JSONResponse(
            success: false,
            error: ErrorInfo(
                message: error.localizedDescription,
                code: errorCode
            )
        )
        outputJSON(response, logger: logger)
    } else {
        fputs("❌ \(error.localizedDescription)\n", stderr)
    }
}
